/* revised by nir tzahcar, 13/03/2003
 *
 *  thread suspension upon creation is achieved as follows:
 *  for each thread( the job thread and the counter thread)  
 *  there exists a cond variable.
 *  when the Thread object is created, the job thread is created,
 *  and its start function is a special wrapper function. what it does is
 *  making it sleep on his conditional variable, and then execute the REAL func.
 *
 *  when the Thread object enters the "run" methode, it creates the counter thread
 *  with the same methode, and then broadcasts to both of them( which will wake them up... )
 *
 *  ******: i use conditional variables in thread creation and not signals
 *          to avoid a race condition which might occure if i used signals.
 *          
 *
 *  threads suspension during execution is achieved with signals.
 *  mainly SIGTSTP and SIGCONT. 
 *
 *  note: i use a non portable feature, which allows a thread to unlock a mutex locked by
 *        a different thread!!!!!
 *
*/

#include "Thread.h"

int ID=0;

/* 
   readString reads a string from the standard input. 
   Safe to use in multi-threaded system.
   String must be up to 1000 characters long and ends when the user presses <Enter>.
   The string is allocated dynamically, and therefore must be deleted afterwards.
*/
char*
readString()
{
  char* str=new char[1000];
  int i=0;
  while((str[i++]=getchar())!='\n');
  str[i]='\0';
  return str;
}

pthread_mutex_t counterLock = PTHREAD_MUTEX_INITIALIZER; //used to make the lock/unlock operations atomic

long timeLeft; // global to be accessable by both counter and run

void* current; // holds a pointer to running Thread object

void* counter(void*); // the counting function



/* args:
      entry: the function to start the thread from.
      arg: the arguments for entry.

      the goal of this function is to simulate a thread suspended.
      it will not continue execution untill the cond is released

*/
void *
suspend_entry(void *arg)
{
  entry_point *p = (entry_point *)arg;
  void*(*entry)(void*) = p->entry;
  /* when we enter here, the mutex is already locked. */
  pthread_cond_wait(p->cond,p->mutex);
  pthread_mutex_unlock(p->mutex);
  return entry(p->arg);
}


// arguments: entry - thread function, arg - argument for entry
Thread::Thread(void*(*entry)(void*),void *arg)
{
  int n;
  entry_point *p;
  myID=ID;
  ID++;

  
  timer_cond = (pthread_cond_t *) malloc(sizeof(pthread_cond_t));
  if (timer_cond == NULL) delete this;
  job_cond = (pthread_cond_t *) malloc(sizeof(pthread_cond_t));
  if (job_cond == NULL) delete this;
  timer_mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
  if (timer_mutex == NULL) delete this;
  job_mutex = (pthread_mutex_t* ) malloc(sizeof(pthread_mutex_t));
  if (job_mutex == NULL) delete this;
  
  if ( pthread_cond_init(timer_cond,NULL) == -1)  /* initialize the cond stracture */
    delete this;

  if ( pthread_cond_init(job_cond,NULL) == -1)  /* initialize the cond stracture */
    delete this;

  
  if (pthread_mutex_init(timer_mutex,NULL) == -1 )
    delete this;
  
  if (pthread_mutex_init(job_mutex,NULL) == -1 )
    delete this;


  p = new entry_point(entry,arg,job_cond,job_mutex);

  if (! p)
    delete this;
  
  /* starting a new thread, with defualt attributes. */
  pthread_mutex_lock(p->mutex);
  n=pthread_create(&tid,NULL,suspend_entry,p);
  /* is ignored in posix 
  pthread_setconcurrency(2); 
  if(n!=0)
    delete this;
  */
  blocked=0;
  zombie=0;
}

Thread::~Thread()
{
  if (timer_cond != NULL )
    pthread_cond_destroy(timer_cond);
  if (job_cond != NULL )
    pthread_cond_destroy(job_cond);
  if(timer_mutex != NULL )
    pthread_mutex_destroy(timer_mutex);
  if(job_mutex != NULL )
    pthread_mutex_destroy(job_mutex);
  
  pthread_kill(tid,SIGTERM);
}

int
Thread::isBlocked()
{
  return blocked;
}

void Thread::block(){
  if(this==(Thread*)current){
    blocked=1;
    suspend();
  }
}

void
Thread::release()
{
  blocked=0;
}

void
Thread::suspend()
{ 
  pthread_kill(tid,SIGSTOP);
}

int
Thread::getID()
{
  return myID;
}

void
Thread::exit()
{
  TRACE(": exit\n");
  blocked = 1;
  zombie = 1;

  pthread_kill(tid,SIGSTOP);

  // this is a bug. will b fixed later.
  //pthread_join(tid,NULL);
  
}

int
Thread::run(int time)
{
  int i,n;
  pthread_t count; //thr_id of the counter
  entry_point *p;
  
  if(zombie)  // if the thread is zombie.
	  return -2;
  //if blocked return -1
  if(blocked)
    return -1;
  
  p = new entry_point(counter,(void*)time,timer_cond,timer_mutex);
  
  current=(void*)this; //update current running thread
  timeLeft=-1;
  
  //initialize counter and run both counter and this Thread if fail anywhere return -1
  pthread_mutex_lock(p->mutex);
  n=pthread_create(&count,NULL,suspend_entry,p);
  

  if(n!=0)
    return -1;
  // do we realy need this ??? 
  /* n=thr_setprio(count,1);
  if(n!=0)
  return -1; */

  /* to continue both threads, will broadcast on cond , and job_cond!! */
  pthread_mutex_lock(timer_mutex);
  pthread_mutex_lock(job_mutex);
  
  pthread_cond_broadcast(timer_cond);
  pthread_cond_broadcast(job_cond);

  /* make sure to wakeup thread sleeping on signal!! */
  pthread_kill(tid,SIGCONT);
  
  pthread_mutex_unlock(timer_mutex);
  pthread_mutex_unlock(job_mutex);

  //wait for counter to finish
  TRACE("going to wait for counter thread to finish!! \n");
  i=pthread_join(count,NULL);

  TRACE("counter thread finished. \n");
  
  //return the remaining amount of time
  return timeLeft;
}



Mutex::Mutex()
{
  pthread_mutex_init(&m,NULL);
  head=tail=NULL;
  size=0;
}


Mutex::~Mutex()
{
  pthread_mutex_destroy(&m);
}

// lock the Mutex to thread t : if Mutex is not free, t will be blocked
int
Mutex::lock(Thread* t)
{
  int n;
  List* temp;
	
  n=pthread_mutex_lock(&counterLock);// avoid end of time quantum (become atomic)
  temp=new List(t);

  // if the Mutex is locked, add t to list of blocked threads
  if(size>1){
    tail->next=temp;
    tail=tail->next;
    size++;
  }
  if(size==1){
    tail=head=temp;
    size++;
  }

  if(size==0)
    size=1;

  

  //if the Mutex is not free suspend t
  if(size>1){
    pthread_mutex_unlock(&counterLock);//19.04.04
    t->block();
  }else
	pthread_mutex_unlock(&counterLock);//19.04.04
  n=pthread_mutex_lock(&m);
  return n;
}

int
Mutex::unlock()
{
  int n;
  List* temp;
  pthread_mutex_lock(&counterLock);//avoid end of time quantum (atomic)

  //if there are no blocked threads just free the Mutex
  if(size==1){
    n=pthread_mutex_unlock(&m);
    size=0;
  }

  //if there are blocked threads unblock the first one
 if(size>1){  
	if( ! (head->t->isBlocked())){               //3.05.04
	  pthread_mutex_unlock(&counterLock);        //3.05.04
	  while( ! (head->t->isBlocked()));          //19.04.04
	  pthread_mutex_lock(&counterLock); }        //3.05.04      
    head->t->release();
    temp=head;
    head=head->next;
    delete temp;
    n=pthread_mutex_unlock(&m);
    size--;
  }
  pthread_mutex_unlock(&counterLock);//free counter
  return n;
}


//returns a pointer to the running Thread
Thread* self()
{
  return (Thread*)current;
}


//the time quantum counting thread/function
void*
counter(void *arg)
{
  clock_t start=0,end=0;
  struct tms _time;
  //extract info from argument
  clock_t time=((int)arg);
  
  //use this mutex to achieve atomic action of Mutex::lock/unlock
  pthread_mutex_init(&counterLock,NULL);


  times(& _time);
  
  start= _time.tms_utime + _time.tms_stime + _time.tms_cutime + _time.tms_cstime ;

  while(end<=(start+time) && (self()->isBlocked()==0)){
    times(& _time);
    end = _time.tms_utime + _time.tms_stime + _time.tms_cutime + _time.tms_cstime ;
  }

  if(end>=start+time)
    timeLeft=0;
  else
    timeLeft=(int ) (time-(end-start));

  pthread_mutex_lock(&counterLock);//enter this section iff not in Mutex::lock/unlock
  if(self()->isBlocked()==0)
    self()->suspend();//suspend current Thread when time is up if it is not already suspended
  pthread_mutex_unlock(&counterLock);
  
  pthread_mutex_destroy(&counterLock);
  pthread_exit(NULL); // end this thread
  return 0;
}

